package com.duckclouddrive.controller;


import com.duckclouddrive.annotation.GlobalInterceptor;
import com.duckclouddrive.annotation.VerifyParam;
import com.duckclouddrive.component.RedisComponent;
import com.duckclouddrive.entity.config.AppConfig;
import com.duckclouddrive.entity.constants.Constants;
import com.duckclouddrive.entity.dto.CreateImageCode;
import com.duckclouddrive.entity.dto.SessionWebUserDto;
import com.duckclouddrive.entity.dto.UserSpaceDto;
import com.duckclouddrive.entity.enums.VerifyRegexEnum;
import com.duckclouddrive.entity.po.UserInfo;
import com.duckclouddrive.entity.vo.ResponseVO;
import com.duckclouddrive.exception.BusinessException;
import com.duckclouddrive.service.EmailCodeService;
import com.duckclouddrive.service.UserInfoService;
import com.duckclouddrive.utils.StringTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;

@RestController("userInfoController")
public class AccountController extends ABaseController {
    private static final Logger logger = LoggerFactory.getLogger(AccountController.class);

    private static final String CONTENT_TYPE = "Content-Type";
    private static final String CONTENT_TYPE_VALUE = "application/json;charset=UTF-8";

    @Resource
    private UserInfoService userInfoService;
    @Resource
    private EmailCodeService emailCodeService;
    @Resource
    private AppConfig appConfig;
    @Resource
    private RedisComponent redisComponent;

    /**
     * 生成验证码并返回给客户端
     *
     * @param response HTTP响应对象，用于向客户端输出数据
     * @param session  HTTP会话对象，用于保存验证码数据
     * @param type     验证码类型，决定验证码存储的会话属性名称
     * @throws IOException 当向客户端输出流发生错误时抛出
     */
    @RequestMapping("/checkCode")
    public void checkCode(HttpServletResponse response, HttpSession session, Integer type) throws IOException {
        // 创建验证码对象，参数分别为宽度、高度、字符数、干扰线数量
        CreateImageCode vCode = new CreateImageCode(130, 38, 5, 10);

        // 设置响应头，禁止浏览器缓存
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);

        // 设置响应内容类型为JPEG图片
        response.setContentType("image/jpeg");

        // 获取生成的验证码文本
        String code = vCode.getCode();

        // 根据验证码类型，存储验证码到会话中，默认类型存储为CHECK_CODE_KEY属性
        if (type == null || type == 0) {
            session.setAttribute(Constants.CHECK_CODE_KEY, code);
        } else {
            // 非默认类型存储为CHECK_CODE_KEY_EMAIL属性
            session.setAttribute(Constants.CHECK_CODE_KEY_EMAIL, code);
        }

        // 将验证码图片输出到响应流中
        vCode.write(response.getOutputStream());
    }


    /**
     * 发送电子邮件验证码
     * <p>
     * 该方法用于向用户提供的电子邮件地址发送验证码该验证码用于确认用户身份，常用于注册、密码重置等操作
     * 方法首先检查用户提交的图形验证码是否正确，如果正确，则通过电子邮件服务发送验证码；否则，抛出异常
     *
     * @param session   HTTP会话对象，用于获取会话信息
     * @param email     用户的电子邮件地址，用于接收验证码
     * @param checkCode 用户输入的图形验证码，用于验证用户操作的合法性
     * @param type      验证码类型，用于区分不同操作场景下的验证码如注册、密码重置等
     * @return 返回一个响应对象，表示操作结果
     */
    @RequestMapping("/sendEmailCode")
    @GlobalInterceptor(checkLogin = false,checkParams = true)
    public ResponseVO sendEmailCode(HttpSession session,
                                    @VerifyParam(required = true) String email,
                                    @VerifyParam(required = true) String checkCode,
                                    @VerifyParam(required = true) Integer type) {
        try {
            // 验证用户输入的图形验证码是否正确
            if (!checkCode.equalsIgnoreCase((String) session.getAttribute(Constants.CHECK_CODE_KEY_EMAIL))) {
                // 如果验证码不正确，抛出业务异常
                throw new BusinessException("图片验证码不正确");
            }
            // 调用服务发送电子邮件验证码
            emailCodeService.sendEmailCode(email, type);
            // 返回成功响应对象，不包含数据
            return getSuccessResponseVO(null);
        } finally {
            // 清除会话中的图形验证码，以确保安全性
            session.removeAttribute(Constants.CHECK_CODE_KEY_EMAIL);
        }
    }

    /**
     * @Description: 注册
     * @auther: laoluo
     * @date: 20:39 2023/4/1
     * @param: [session, email, nickName, password, checkCode, emailCode]
     * @return: com.easypan.entity.vo.ResponseVO
     */
    @RequestMapping("/register")
    @GlobalInterceptor(checkLogin = false, checkParams = true)
    public ResponseVO register(HttpSession session,
                               @VerifyParam(required = true, regex = VerifyRegexEnum.EMAIL, max = 150) String email,
                               @VerifyParam(required = true, max = 20) String nickName,
                               @VerifyParam(required = true, regex = VerifyRegexEnum.PASSWORD, min = 8, max = 18) String password,
                               @VerifyParam(required = true) String checkCode,
                               @VerifyParam(required = true) String emailCode) {
        try {
            if (!checkCode.equalsIgnoreCase((String) session.getAttribute(Constants.CHECK_CODE_KEY))) {

                throw new BusinessException("图片验证码不正确");

            }
            userInfoService.register(email, nickName, password, emailCode);
            return getSuccessResponseVO(null);
        } finally {
            session.removeAttribute(Constants.CHECK_CODE_KEY);
        }
    }

    @RequestMapping("/login")
    @GlobalInterceptor(checkLogin = false, checkParams = true)
    public ResponseVO login(HttpSession session,
                            @VerifyParam(required = true) String email,
                            @VerifyParam(required = true) String password,
                            @VerifyParam(required = true) String checkCode) {
        try {
            if (!checkCode.equalsIgnoreCase((String) session.getAttribute(Constants.CHECK_CODE_KEY))) {
                throw new BusinessException("图片验证码不正确");
            }
            SessionWebUserDto sessionWebUserDto = userInfoService.login(email, password);
            session.setAttribute(Constants.SESSION_KEY, sessionWebUserDto);
            userInfoService.login(email, password);
            return getSuccessResponseVO(sessionWebUserDto);
        } finally {
            session.removeAttribute(Constants.CHECK_CODE_KEY);
        }
    }


    @RequestMapping("/resetPwd")
    @GlobalInterceptor(checkLogin = false, checkParams = true)
    public ResponseVO resetPwd(HttpSession session,
                               @VerifyParam(required = true, regex = VerifyRegexEnum.EMAIL, max = 150) String email,
                               @VerifyParam(required = true, regex = VerifyRegexEnum.PASSWORD, min = 8, max = 18) String password,
                               @VerifyParam(required = true) String checkCode,
                               @VerifyParam(required = true) String emailCode) {
        try {
            if (!checkCode.equalsIgnoreCase((String) session.getAttribute(Constants.CHECK_CODE_KEY))) {
                throw new BusinessException("图片验证码不正确");
            }
            userInfoService.resetPwd(email, password, emailCode);
            return getSuccessResponseVO(null);
        } finally {
            session.removeAttribute(Constants.CHECK_CODE_KEY);
        }
    }


    /**
     * 根据用户ID获取头像
     *
     * 本方法用于响应获取用户头像的请求它首先检查用户ID是否提供，并尝试在服务器上找到对应用户头像的文件
     * 如果用户头像文件不存在，它将检查默认头像文件是否存在，如果也不存在，则显示没有默认图像的信息
     * 最后，它将找到的头像文件内容以JPEG格式发送回客户端
     *
     * @param response 用于将头像文件发送回客户端的HttpServletResponse对象
     * @param userId 从请求URL中获取的用户ID，用于定位特定用户的头像文件
     */
    @RequestMapping("/getAvatar/{userId}")
    @GlobalInterceptor(checkParams = true)
    public void getAvatar(
            HttpServletResponse response,
            @VerifyParam(required = true) @PathVariable("userId") String userId) {
        // 定义头像文件夹的名称
        String avatarFolderName = Constants.FILE_FOLDER_FILE + Constants.FILE_FOLDER_AVATAR_NAME;
        // 构建头像文件夹的完整路径，并检查该文件夹是否存在，如果不存在则创建
        File folder = new File(appConfig.getProjectFolder() + avatarFolderName);
        if (!folder.exists()) {
            folder.mkdir();
        }
        // 构建用户头像文件的完整路径
        String avatarPath = appConfig.getProjectFolder() + avatarFolderName + userId + Constants.AVATAR_SUFFIX;
        File file = new File(avatarPath);
        // 检查用户头像文件是否存在
        if (!file.exists()) {
            // 如果用户头像文件不存在，检查默认头像文件是否存在
            if (!new File(appConfig.getProjectFolder() + avatarFolderName + Constants.AVATAR_DEFUALT).exists()) {
                //System.out.println("fiel:" + appConfig.getProjectFolder() + avatarFolderName + Constants.AVATAR_DEFUALT);
                printNoDefaultImage(response);
            }
            // 如果默认头像文件存在，使用默认头像文件的路径
            avatarPath = appConfig.getProjectFolder() + avatarFolderName + Constants.AVATAR_DEFUALT;
        }
        // 设置响应的内容类型为JPEG图像
        response.setContentType("image/jpeg");
        // 将头像文件的内容发送给客户端
        readFile(response, avatarPath);
    }

    /**
     * 向客户端输出提示信息，用于在头像目录下未找到默认头像时通知用户
     * 此方法主要用于Web环境中，当系统预设的默认头像图片（default_avatar.jpg）不存在于指定目录时，
     * 向用户输出一条消息提示请在头像目录下放置默认头像default_avatar.jpg
     *
     * @param response Servlet响应对象，用于设置响应头、响应状态和向客户端输出内容
     */
    private void printNoDefaultImage(HttpServletResponse response) {
        // 设置响应类型为文本
        response.setHeader(CONTENT_TYPE, CONTENT_TYPE_VALUE);
        // 设置HTTP状态码为200，表示请求成功
        response.setStatus(HttpStatus.OK.value());
        PrintWriter writer = null;
        try {
            // 获取响应的输出流，用于向客户端输出文本
            writer = response.getWriter();
            // 输出提示信息
            writer.print("请在头像目录下放置默认头像default_avatar.jpg");
            // 关闭输出流，释放资源
            writer.close();
        } catch (Exception e) {
            // 记录输出过程中的异常信息
            logger.error("输出无默认图失败", e);
        } finally {
            // 确保在finally块中关闭输出流
            writer.close();
        }
    }

    /**
     * 处理获取用户信息的请求
     * 该方法通过HttpSession获取用户信息，并将其封装到响应对象中
     *
     * @param session HttpSession对象，用于获取用户信息
     * @return 返回封装了用户信息的响应对象
     */
    @RequestMapping("/getUserInfo")
    public ResponseVO getUserInfo(HttpSession session) {
        // 从会话中获取用户信息
        SessionWebUserDto sessionWebUserDto = getUserInfoFromSession(session);
        // 构建并返回成功的响应对象
        return getSuccessResponseVO(sessionWebUserDto);
    }
    /**
     * 处理获取用户空间使用情况的请求
     *
     * @param session HttpSession对象，用于获取用户会话信息
     * @return 返回用户空间使用情况的响应对象
     */
    @RequestMapping("/getUseSpace")
    public ResponseVO getUserSpace(HttpSession session) {
        // 从用户会话中获取用户信息
        SessionWebUserDto sessionWebUserDto = getUserInfoFromSession(session);
        // 通过Redis组件获取用户的空间使用情况
        UserSpaceDto spaceDto = redisComponent.getUserSpaceUse(sessionWebUserDto.getUserId());
        // 构造并返回成功的响应对象，包含用户空间使用情况
        return getSuccessResponseVO(spaceDto);
    }

    @RequestMapping("/logout")
    @GlobalInterceptor(checkParams = true)
    public ResponseVO logut(HttpSession session) {
        session.invalidate();
        return getSuccessResponseVO(null);
    }

    @RequestMapping("/updateUserAvatar")
    @GlobalInterceptor
    /**
     * 更新用户头像
     *
     * 此方法负责处理用户头像的上传和更新 用户首先从会话中获取用户信息，然后将上传的头像文件保存到指定的目录结构中
     * 成功上传后，更新用户信息表中的头像字段，并在会话中更新用户对象的头像信息
     *
     * @param session 当前用户的会话对象，用于获取和更新用户信息
     * @param avatar  用户上传的头像文件
     * @return 返回一个响应对象，表示操作是否成功
     */
    public ResponseVO updateUserAvatar(HttpSession session, MultipartFile avatar) {
        // 从会话中获取用户信息
        SessionWebUserDto webUserDto = getUserInfoFromSession(session);

        // 定义头像文件的根目录
        String baseFolder = appConfig.getProjectFolder() + Constants.FILE_FOLDER_FILE;
        // 构建头像文件夹的完整路径
        File targetFileFolder = new File(baseFolder + Constants.FILE_FOLDER_AVATAR_NAME);
        // 如果头像文件夹不存在，则创建它
        if (!targetFileFolder.exists()) {
            targetFileFolder.mkdirs();
        }

        // 构建用户头像文件的完整路径
        File targetFile = new File(targetFileFolder.getPath() + "/" + webUserDto.getUserId() + Constants.AVATAR_SUFFIX);
        try {
            // 将上传的头像文件保存到目标路径
            avatar.transferTo(targetFile);
        } catch (Exception e) {
            // 如果保存过程中发生异常，则记录错误信息
            logger.error("上传头像失败", e);
        }

        // 更新用户信息表中的头像字段，如果字段不为null则更新
        UserInfo userInfo = new UserInfo();
        //本地头像邮箱，所以qq默认头像设置为空
        userInfo.setQqAvatar("");
        userInfoService.updateUserInfoByUserId(userInfo, webUserDto.getUserId());

        // 更新会话中的用户对象的头像信息
        webUserDto.setAvatar(null);
        session.setAttribute(Constants.SESSION_KEY, webUserDto);

        // 返回操作成功的响应对象
        return getSuccessResponseVO(null);
    }
    /**
     * 修改用户密码
     *
     * @param session 用户会话，用于获取用户信息
     * @param password 新密码，要求长度为8到18个字符，符合密码格式
     * @return 返回一个响应对象，包含操作结果
     *
     * 此方法用于处理用户修改密码的请求它首先从会话中获取用户信息，
     * 然后对新密码进行MD5加密处理，并调用服务层方法更新用户信息中的密码
     * 最后返回一个表示操作成功的响应对象
     */
    @RequestMapping("/updatePassword")
    @GlobalInterceptor(checkParams = true)
    public ResponseVO updatePassword(HttpSession session,
                                     @VerifyParam(required = true, regex = VerifyRegexEnum.PASSWORD, min = 8, max = 18) String password) {
        // 从会话中获取用户信息
        SessionWebUserDto sessionWebUserDto = getUserInfoFromSession(session);
        // 创建一个UserInfo对象，用于封装需要更新的用户信息
        UserInfo userInfo = new UserInfo();
        // 将新密码进行MD5加密后设置给UserInfo对象
        userInfo.setPassword(StringTools.encodeByMD5(password));
        // 调用服务层方法，根据用户ID更新用户信息中的密码
        userInfoService.updateUserInfoByUserId(userInfo, sessionWebUserDto.getUserId());
        // 返回一个表示操作成功的响应对象
        return getSuccessResponseVO(null);
    }

    /**
     * 处理QQ登录请求
     * 该方法用于重定向用户到QQ登录页面，以便进行第三方QQ登录流程
     *
     * @param session HttpSession对象，用于会话管理
     * @param callbackUrl 回调地址，即用户登录成功后需要跳转的URL
     * @return 返回一个ResponseVO对象，包含重定向到QQ登录页面的URL
     *
     * @RequestMapping("qqlogin") 注解用于映射URL请求路径
     * @GlobalInterceptor(checkLogin = false, checkParams = true) 注解用于全局拦截器，此处表示不检查登录状态，但检查参数有效性
     *
     * 方法流程说明：
     * 1. 生成一个随机字符串state，用于防止CSRF攻击
     * 2. 如果回调地址不为空，则将state与回调地址关联保存在会话中
     * 3. 构造QQ授权登录的URL，并进行URL编码处理
     * 4. 返回一个成功响应，其中包含构造的QQ登录URL，引导浏览器重定向到QQ登录页面
     */
    @RequestMapping("qqlogin")
    @GlobalInterceptor(checkLogin = false, checkParams = true)
    public ResponseVO qqlogin(HttpSession session, String callbackUrl) throws UnsupportedEncodingException {
        // 生成state值，用于CSRF保护
        String state = StringTools.getRandomString(Constants.LENGTH_30);
        // 如果回调URL不为空，则将state与回调URL关联保存
        if (!StringTools.isEmpty(callbackUrl)) {
            session.setAttribute(state, callbackUrl);
        }
        // 构造QQ授权登录的URL
        String url = String.format(appConfig.getQqUrlAuthorization(), appConfig.getQqAppId(), URLEncoder.encode(appConfig.getQqUrlRedirect(), "utf-8"), state);
        // 返回成功响应，包含QQ登录URL
        return getSuccessResponseVO(url);
    }
    /**
     * 处理QQ登录的回调请求
     *
     * @param session HTTP会话，用于存储用户信息
     * @param code QQ登录返回的授权码，用于获取用户信息
     * @param state 用于防止跨站请求伪造（CSRF）的随机字符串
     * @return 返回一个响应对象，包含用户的回调URL和用户信息
     *
     * 此方法处理QQ登录后的回调请求，通过授权码code获取用户信息，
     * 并将用户信息存储到会话中，同时返回一个包含回调URL和用户信息的响应对象
     */
    @RequestMapping("qqlogin/callback")
    @GlobalInterceptor(checkLogin = false, checkParams = true)
    public ResponseVO qqLoginCallback(HttpSession session,
                                      @VerifyParam(required = true) String code,
                                      @VerifyParam(required = true) String state) {
        // 通过授权码code获取用户信息
        SessionWebUserDto sessionWebUserDto = userInfoService.qqLogin(code);
        // 将用户信息存储到会话中
        session.setAttribute(Constants.SESSION_KEY, sessionWebUserDto);
        // 构建返回的结果对象，包含回调URL和用户信息
        Map<String, Object> result = new HashMap<>();
        // 从会话中获取并设置回调URL
        result.put("callbackUrl", session.getAttribute(state));
        // 设置用户信息
        result.put("userInfo", sessionWebUserDto);
        // 返回成功的响应对象，包含结果数据
        return getSuccessResponseVO(result);
    }
}
